package dom.xpath.query.xpath.internal;

import dom.xpath.data.Data;
import dom.xpath.query.xpath.XPContext;
import dom.xpath.query.xpath.expr.FTOption;
import dom.xpath.query.xpath.values.NodeSet;
import dom.xpath.util.Array;
import dom.xpath.util.Token;

/**
 * This expression retrieves the ids of indexed fulltext terms.
 * 
 * @author Workgroup DBIS, University of Konstanz 2005-07, ISC License
 * @author Tim Petrowsky
 * @author Christian Gruen
 */
public final class FTIndex extends InternalExpr {
  /** Token to be found in the index. */
  private final byte[] token;
  /** FullText options. */
  FTOption option;
  /** Ids as result of index requerst with tok. */
  int[][] ids;
  /** Flag for simple ftcontains querie. **/
  boolean simple = true;
  /** Flag for tokens containing only one single word, 
   * like 'usability' (not 'usability testing'). **/
  boolean single = false;

  /**
   * Constructor.
   * @param tok index token
   * @param opt FTOption for index token
   * @param sim flag for simple ftcontains queries
   * @param sing flag for single word queries
   */
  public FTIndex(final byte[] tok, final FTOption opt, 
      final boolean sim, final boolean sing) {
    token = tok;
    option = opt;
    simple = sim;
    single = sing;
  }
  
  /**
   * Get token for index access.
   * 
   * @return token
   */
  public byte[] getToken() {
    return token;
  }
  
  /**
   * Get FTOptions for index access.
   * 
   * @return ftoptions
   */
  public FTOption getFTOption() {
    return option;
  }

  @Override
  public NodeSet eval(final XPContext ctx) {
    final Data data = ctx.local.data;
    
    if(single) {
      ids = data.ftIDs(token, option);
      if(simple) {
        // simple ftcontains don't need pos-values out of the index
        return new NodeSet(Array.extractIDsFromData(ids), ctx);
      } else {
        return new NodeSet(new int[0], ctx, ids);
      }
    } else {
      evalNonSingle(data);
      if(simple) {
        // simple ftcontains don't need pos-values out of the index
        return new NodeSet(Array.extractIDsFromData(ids), ctx);
      } else {
        return new NodeSet(new int[0], ctx, ids);
      }
    }
  }

  @Override
  public String toString() {
    return getClass().getSimpleName() + "(" + Token.string(token) + ")";
  }
  
  /**
   * Evalutation off non single word queries.
   * 
   * @param data data referenze
   */
  private void evalNonSingle(final Data data) {
    byte[] tok;
    int i = Token.indexOf(token, ' ');
    tok = new byte[i];
    System.arraycopy(token, 0, tok, 0, i);
    int[][] tmp = data.ftIDs(tok, option);
    
    if (tmp == null) {
      return;
    }
    
    int lastpre = -1;
    byte[] db = null;
    int count = 0;
    int[][] res = new int[2][tmp[0].length];
    int j; // runvariable for token
    int k; // runvaribale for db
    
    for (i = 0; i < tmp[0].length; i++) {
      j = 0;
      k = 0;
      if (lastpre != tmp[0][i]) {
        lastpre = tmp[0][i];
        db = data.text(lastpre);
      }
      
      // "usability" not in "usability testing"
      // "mexico" not in "new mexico"
      // "mexico" not in "new mexico city"
      if (db.length >= tmp[1][i] + token.length) {
        if (option.ftCase.equals(FTOption.CASE.INSENSITIVE)) {
          while (j < token.length && k < db.length) { 
            if (Token.lc(db[tmp[1][i] + k]) == Token.lc(token[j])) { 
                j++;
                k++;
            } else if (!Token.ftChar(db[tmp[1][i] + k])) {
              while (db.length > tmp[1][i] + k 
                  && !Token.ftChar(db[tmp[1][i] + k])) k++;
            } else {
              break;
            }
          }       
        } else if (option.ftCase.equals(FTOption.CASE.SENSITIVE)) {
          while (j < token.length && k < db.length) { 
            if (db[tmp[1][i] + k] == token[j]) { 
                j++;
                k++;
            } else if (!Token.ftChar(db[tmp[1][i] + k])) {
              while (db.length > tmp[1][i] + k 
                  && !Token.ftChar(db[tmp[1][i] + k])) k++;
            } else {
              break;
            }
          }    
          
        } else  if (option.ftCase.equals(FTOption.CASE.UPPERCASE)) {
          while (j < token.length && k < db.length) { 
            if (db[tmp[1][i] + k] == Token.uc(token[j])) { 
                j++;
                k++;
            } else if (!Token.ftChar(db[tmp[1][i] + k])) {
              while (db.length > tmp[1][i] + k 
                  && !Token.ftChar(db[tmp[1][i] + k])) k++;
            } else {
              break;
            }
          }    
        } else {
          while (j < token.length && k < db.length) { 
            if (db[tmp[1][i] + k] == Token.lc(token[j])) { 
                j++;
                k++;
            } else if (!Token.ftChar(db[tmp[1][i] + k])) {
              while (db.length > tmp[1][i] + k 
                  && !Token.ftChar(db[tmp[1][i] + k])) k++;
            } else {
              break;
            }
          }    
        }

        
        if (j == token.length) {
          res[0][count] = tmp[0][i];
          res[1][count++] = tmp[1][i];
        } 
      }
    }
    
    ids = new int[2][];
    ids[0] = Array.finish(res[0], count);
    ids[1] = Array.finish(res[1], count);
  }
  
}
